"""
Factory for tags creating.
"""
from djwheel import modelutils
from django.db import models
from django.utils.translation import ugettext_lazy as _


def tagable_model(get_tagmodel, get_relmodel):
    """
    Create abstract tagable model 
    """    
    class TagableModel(models.Model):        
        tags_as_string = models.TextField(_('Tags'), help_text=_('comma separated'))        

        _get_tagmodel = staticmethod(get_tagmodel)
        _get_relmodel = staticmethod(get_relmodel)
        
        def tags(self):
            """Returns tags objects"""
            Tag = self._get_tagmodel()
            return (Tag.objects.get(name=tagname) for tagname in self.tags_as_list())
            
        def tags_as_list(self):
            """Returns tags as strings"""
            tagslist = self.tags_as_string.split(',')
            return sorted(set(unicode.lower(unicode.strip(s)) for s in tagslist))
        
        @classmethod
        def _exclude_tags_for_ids(cls):
            return []
                    
        def save(self):        
            super(TagableModel, self).save()
            
            tags = self.tags_as_list()
            self.tags_as_string = ', '.join(tags)

            Tag = self._get_tagmodel()
            TagRelations = self._get_relmodel()
            for tagname in tags:            
                try:                
                    tag = Tag.objects.get(name=tagname)                
                except Tag.DoesNotExist:
                    tag = Tag()
                    tag.name = tagname
                    tag.count = 0
                    tag.save()
                    
                try:
                    rel = TagRelations.objects.get(tag=tag, key_object=self)
                except TagRelations.DoesNotExist:                 
                    rel = TagRelations()
                    rel.key_object = self
                    rel.tag = tag
                    rel.save()
            
        def delete(self):
            TagRelations = self._get_relmodel()
            for tag in self.tags():
                rel = TagRelations.objects.get(tag=tag, key_object=self)
                rel.delete()                    
            super(TagableModel, self).delete()
                    
        class Meta:
            abstract = True
            
    return TagableModel


def tagmodel(KEY_MODEL):
    """
    Create abstract model for tag 
    """    
    class Tag(models.Model):         
        name = models.CharField('Title', editable=False, max_length=128)
        count = models.PositiveIntegerField('Count', editable=False, default=0)
        
        _max_count = None
        _min_count = None                    
        _get_relmodel = staticmethod(KEY_MODEL._get_relmodel)
        
        @classmethod
        def get_max_count(cls):
            """Count of usage most popular tag"""
            if cls._max_count is None: 
                cls._refresh_minmax()
            return cls._max_count
    
#        @classmethod
#        def get_min_count(cls):
#            """Count of usage most rare tag"""
#            if cls._min_count is None: 
#                cls._refresh_minmax()
#            return cls._min_count
        
#        min_count = modelutils.create_min('count') 
        max_count = modelutils.create_max('count') 
        
        @classmethod
        def _refresh_minmax(cls):
#            cls._min_count = cls.min_count()
            cls._max_count = cls.max_count()         
    
        def abs_percent(self):
            """Percent of usage compared with all tags"""
            return int(float(self.count) / self._get_relations_model().get_count() * 100 ) 
            
        def rel_percent(self):
            """Percent of usage compared with most popular tag usage""" 
            return int(float(self.count) / self.get_max_count() * 100) 
    
        def delete(self):
            super(Tag, self).delete()
            self._refresh_minmax()
    
        def save(self):
            super(Tag, self).save()
            self._refresh_minmax()
    
        def __unicode__(self):
            return u'"%s"' % self.name
    
        @classmethod
        def get_popular(cls, min_percent=10):
            """Returns list of tags with rel_percent() great that min_percent"""
            return (obj for obj in cls.get_all() 
                if obj.rel_percent() > min_percent)
        
        @classmethod
        def get_all(cls):
            """
            Return all tags respect to key_models's _exclude_tags_for_ids() 
            """
            skip = KEY_MODEL._exclude_tags_for_ids()
            if skip:
                tag_ids = [rel.tag_id for rel in cls._get_relmodel().objects.filter(key_object__in=skip)]
                tags = []
                for tag in cls.objects.all():
                    c = tag.get_count(tag_ids)
                    if c != 0:
                        tag.count = c
                        tags.append(tag)
                return tags
            else:
                return cls.objects.all()
        
        def get_count(self, tag_ids=None):
            """
            Return count respect to key_models's _exclude_tags_for_ids() 
            """
            if tag_ids:  # tag_ids is optimization for calling from get_all() 
                if self.id in tag_ids:
                    tag_ids.remove(self.id)
                    return self.count - 1
            else:
                skip = KEY_MODEL._exclude_tags_for_ids()
                if skip:
                    return self._get_relmodel().objects.exclude(key_object__in=skip).filter(tag=self).count()
            return self.count
        
        class Meta:
            verbose_name = _('tag')
            verbose_name_plural = _('tags')
            ordering = ['name']
            abstract = True
                        
    return Tag
        

def relmodel(KEY_MODEL):
    """
    Create abstract model for tag relations 
    """        
    class Rel(models.Model):
        key_object = models.ForeignKey(KEY_MODEL)
        tag = models.ForeignKey(KEY_MODEL._get_tagmodel())
        
        _count = None
        _get_tagmodel = staticmethod(KEY_MODEL._get_tagmodel)
        
        @classmethod
        def get_count(cls):
            """Return number of all tags"""
            if cls._count is None: 
                cls._refresh_count()
            return cls._count 
    
        @classmethod
        def get_ids_by_tag(cls, tag):
            """Return list of key_object's ids by tag or tag_id"""
            tagmodel = cls._get_tagmodel()
            if not isinstance(tag, tagmodel):
                tag = tagmodel.objects.get(id=tag)
            return [rel.key_object_id for rel in cls.objects.filter(tag=tag)]        

        @classmethod
        def _refresh_count(cls):
            cls._count = cls.objects.all().count()
            
        def _refresh_tag_count(self):
            tagcount = self.__class__.objects.filter(tag=self.tag).count()
            if tagcount == 0: 
                self.tag.delete()
            else:            
                self.tag.count = tagcount 
                self.tag.save()
            
        def save(self):
            super(Rel, self).save()
            self._refresh_tag_count()
            self._refresh_count()
            
        def delete(self):
            super(Rel, self).delete()
            self._refresh_tag_count()
            self._refresh_count()
            
        def __unicode__(self):
            return u'%s - %s' % (self.key_object, self.tag)         
                        
        class Meta:
            abstract = True
            
    return Rel
